package com.cn.sys.main.aop;

import java.lang.reflect.Method;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.cn.sys.common.Constants;
import com.cn.sys.main.annotation.OperationDescription;
import com.cn.sys.main.entity.SysOpeLog;
import com.cn.sys.main.entity.SysUser;
import com.cn.sys.main.service.SysOpeLogService;

@Component
@Aspect
public class SysLoggerAop {

	private static Logger logger = LoggerFactory.getLogger(SysLoggerAop.class);
	
	//缓存有@OperationDescription方法参数名称 
    private static Map<String, String[]> parameterNameCaches = new ConcurrentHashMap<String, String[]>();
    //缓存SPEL Expression
    private static Map<String, Expression> spelExpressionCaches = new ConcurrentHashMap<String, Expression>();
    private static ExpressionParser parser = new SpelExpressionParser();
    private static LocalVariableTableParameterNameDiscoverer parameterNameDiscovere =  new LocalVariableTableParameterNameDiscoverer();
    
	@Autowired
	private SysOpeLogService sysOpeLogService;
	
	@Pointcut("execution (* com.cn..service.impl.*.*(..))")  
    public void pointcut(){}
      
    //方法执行前调用  
    @Before("pointcut()")  
    public void before() {
    }  
      
    //方法执行的前后调用  
    @Around(value = "pointcut() && @annotation(annotation)")
    public Object around(ProceedingJoinPoint point, OperationDescription annotation) throws Throwable{
    	Object object = null;
    	try {
			object = point.proceed();
			this.saveLog(point, annotation);
		} catch (Exception e) {
			e.printStackTrace();
			logger.error("AOP记录操作日志出错: ["+e.getMessage()+"]");
		}
        return object;  
    }
    
    private void saveLog(ProceedingJoinPoint joinPoint, OperationDescription annotation) throws Exception {
        String content = this.executeTemplate(annotation.content(), joinPoint);
        String operateObj = annotation.operateObj();
        String type = annotation.operateType();
        
        SysOpeLog opeLog = new SysOpeLog();
        opeLog.setOperateType(type);
        opeLog.setOperateUser(getOperateUser());
        opeLog.setContent(content);
        opeLog.setOperateTime(new Date());
        opeLog.setOperateObj(operateObj);
        try {
			sysOpeLogService.save(opeLog);
		} catch (Exception e) {
			e.printStackTrace();
			logger.info("保存操作日志出错: "+e.getMessage());
		}
    }
    
    /** 
     * 解析执行OperationDescription 的description模板。 
     *  
     * @param template 
     * @param joinPoint 
     * @return 
     */  
    private String executeTemplate(String template, ProceedingJoinPoint joinPoint) {
        // get method parameter name
        String methodLongName = joinPoint.getSignature().toLongString();
        //System.out.println("executeTemplate--->methodLongName-----:"+methodLongName);
        String[] parameterNames =  parameterNameCaches.get(methodLongName);
        if(parameterNames == null) {
            Method method = this.getMethod(joinPoint);
            parameterNames = parameterNameDiscovere.getParameterNames(method);
            parameterNameCaches.put(methodLongName, parameterNames);
        }
        // add args to expression context  
        StandardEvaluationContext context = new StandardEvaluationContext();
        context.setTypeConverter(new ValueFormatTypeConverter());
        Object[] args = joinPoint.getArgs();
        if(args.length == parameterNames.length) {
            for(int i=0, len=args.length; i<len; i++){
            	context.setVariable(parameterNames[i], args[i]);
            }
        }
        //cacha expression  
        Expression expression = spelExpressionCaches.get(template);
        if(expression == null) {
            expression = parser.parseExpression(template, new TemplatedParserContext());
            spelExpressionCaches.put(template, expression);
        }
        String value = expression.getValue(context, String.class);
        return value;
    }
    
    /** 
     * 获取当前执行的方法 
     * @param joinPoint 
     * @return 
     */  
    private Method getMethod(ProceedingJoinPoint joinPoint) {  
        String methodLongName = joinPoint.getSignature().toLongString();
        methodLongName += " throws java.lang.Exception";
        Method[] methods = joinPoint.getTarget().getClass().getMethods();
        Method method = null;
        for(int i=0, len=methods.length; i<len; i++) {
            if(StringUtils.equals(methods[i].toString(), methodLongName)) {
                method = methods[i];
                break;
            }
        }
        return method;
    }
    
    private String getOperateUser(){
    	String userName = "";
    	HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
	    if(request!=null){
	    	SysUser user = (SysUser) request.getSession().getAttribute(Constants.USER_CONTEXT);
	    	if(user!=null){
	    		userName = user.getUserName();
	    	}
	    }
	    return userName;
    }
}
